﻿using System.Collections.Generic;
using System.Linq;
using Web_App___AWS_ELB_Monitor.Entities;
using System;
using Amazon.CloudWatch.Model;
using System.Collections;
using Web_App___AWS_ELB_Monitor.Foundation;

namespace Web_App___AWS_ELB_Monitor
{
    public static class DataPointExtensions
    {
        private static readonly DateTimeOffset MillitimeFirst = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero);

        public static double ToMillitimeDatestamp(this DateTimeOffset dateForConvertion)
        {
            return
                new TimeSpan(dateForConvertion.Ticks - MillitimeFirst.Ticks).TotalMilliseconds;
        }

        public static string ToGoogleChartDatedDataTableRows(this IEnumerable<DataPoint> data)
        {
            var jsonItems = data.Select(dataItem =>
                                        "[new Date(" + DateTimeOffset.Now.ToMillitimeDatestamp() + "), " + dataItem.Value + "]");

            return String.Join(",", jsonItems.ToArray());
        }

        /// <summary>
        /// Group data values by date
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public static IEnumerable<DatePointGraph> ToDatePointGraph(this IEnumerable<IEnumerable<DataPoint>> data)
        {
            //get a list of all dates in our column data (so therefore, all rows)
            var allDates = data.ToList().SelectMany(dataGroup => dataGroup.Select(item => item.TimeStamp)).Distinct();

            //convert to hash dictionary for quick lookups (and so we don't repeat unique date sorting)
            var dataHashList = new List<Hashtable>();
            foreach (var dataPointList in data)
            {
                Hashtable dataList = new Hashtable();
                foreach (var dataPoint in dataPointList)
                {
                    if (!dataList.ContainsKey(dataPoint.TimeStamp))
                    {
                        dataList.Add(dataPoint.TimeStamp, dataPoint);
                    }
                }
                dataHashList.Add(dataList);
            }

            return allDates.Select(date =>
                                   new DatePointGraph
                                   {
                                       TimeStamp = date,
                                       Value = dataHashList.Select(dataItem => (dataItem.ContainsKey(date))
                                                                                   ? ((DataPoint)dataItem[date]).Value
                                                                                   : 00).Reverse()
                                   }
                );
        }

        /// <summary>
        /// Convert grouped data values by date to Json compatible with Google DataTables
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public static string ToGoogleChartDatedDataTableRows(this IEnumerable<DatePointGraph> data)
        {
            //foreach unique date, make a row, and get value from each data source for the columns (if it has one, else JS null)
            return String.Join(",", data.Select(dataItem =>

                                                    "[\"" + dataItem.TimeStamp.ToString("HH:mm") + "\"," +
                                                        //foreach collection, add value or null, in column order
                                                    String.Join(",", dataItem.Value)

                                                    + "]"
                                        ).ToArray()
                );
        }


        private static string ToGoogleChartDatedDataTableRows(this IEnumerable<IEnumerable<DataPoint>> data)
        {
            //get a list of all dates in our column data (so therefore, all rows)
            var allDates = data.ToList().SelectMany(dataGroup => dataGroup.Select(item => item.TimeStamp)).Distinct();

            //convert to hash dictionary for quick lookups (and so we don't repeat unique date sorting)
            var dataHashList = new List<Hashtable>();
            foreach (var dataPointList in data)
            {
                Hashtable dataList = new Hashtable();
                foreach (var dataPoint in dataPointList)
                {
                    if (!dataList.ContainsKey(dataPoint.TimeStamp))
                    {
                        dataList.Add(dataPoint.TimeStamp, dataPoint);
                    }
                    else
                    {
                        bool alreadyRecorded = true;
                    }
                }
                dataHashList.Add(dataList);
            }

            //foreach unique date, make a row, and get value from each data source for the columns (if it has one, else JS null)
            return String.Join(",", allDates.Select(date =>

                                                    "[\"" + date.ToString("HH:mm") + "\"," +
                                                        //foreach collection, add value or null, in column order
                                                    String.Join(",", dataHashList.Select(dataItem =>
                                                                                         (dataItem.ContainsKey(date))
                                                                                             ? ((DataPoint)dataItem[date])
                                                                                                   .Value.ToString()
                                                                                             : "null"
                                                                         ).Reverse().ToArray())

                                                    + "]"
                                        ).ToArray()
                );
        }

        private static double GetValueForDataPoint(AnalyticsValueType valueType, Datapoint data)
        {
            switch (valueType)
            {
                case AnalyticsValueType.Sum:
                    return data.Sum;
                    break;
                case AnalyticsValueType.Average:
                    return data.Average;
                    break;
                case AnalyticsValueType.Minimum:
                    return data.Minimum;
                    break;
                case AnalyticsValueType.Maximum:
                    return data.Maximum;
                    break;
                case AnalyticsValueType.SampleCount:
                    return data.SampleCount;
                default:
                    throw new ArgumentOutOfRangeException("valueType");
            }
        }

        /// <summary>
        /// Convert a metric response from AWS to datapoints
        /// </summary>
        /// <param name="stats"></param>
        /// <param name="onlyTime"></param>
        /// <param name="valueType"></param>
        /// <returns></returns>
        public static IEnumerable<DataPoint> ToDataPoints(this GetMetricStatisticsResponse stats, bool onlyTime = true, AnalyticsValueType valueType = AnalyticsValueType.Sum)
        {
            if (stats.GetMetricStatisticsResult != null)
            {
                var data = stats.GetMetricStatisticsResult.Datapoints;
                return data.Select(dataItem =>
                {
                    var timeStamp = dataItem.Timestamp.ToUniversalTime();
                    if (onlyTime)
                    {
                        timeStamp = timeStamp.TodaySameTime();
                    }
                    return new DataPoint
                    {
                        Value = GetValueForDataPoint(valueType, dataItem),
                        TimeStamp = timeStamp
                    };
                }).OrderBy(dataItem => dataItem.TimeStamp);
            }
            else
            {
                return new List<DataPoint>();
            }
        }

        public static string ToJson(this IEnumerable<DatePointGraph> data)
        {
            return String.Join(",", data.Select(graphPoint =>
                               "{\"Date\":\"" + graphPoint.TimeStamp.ToString("HH:mm") + "\",\"Data\":" + "[" +
                               String.Join(",", graphPoint.Value.ToArray()) + "]}").ToArray());
        }

        public static string ToJSon(this IDictionary<string, List<DataPoint>> data)
        {
            return String.Join(",", data.Select(instance =>
                        "{\"Instance\":\"" + instance.Key + "\",\"Data\":" + "[" +
                        String.Join(",", instance.Value.Select(val =>
                        (val != null ?
                        Math.Round(val.Value)
                        : 0.0)
                        ).ToArray()) + "]}").ToArray());
        }

        /// <summary>
        /// This method fills up all the missing minutes in a datapoint collection from 00:00 to 23:59, so that each collection 
        /// always has 1440 data points in it. This is so that when rendering a chart for a new instance that only has a small 
        /// collection of datapoints (i.e. because it may have only been online for 2 minutes) the rendered chart will not look 
        /// stretched and will render at the same scale as other charts, albeit with minimal data displayed)
        /// </summary>
        /// <param name="dataPointCollection"></param>
        /// <param name="utcNow"></param>
        /// <returns></returns>
        public static List<DataPoint> FillDataPoints(this IList<DataPoint> dataPointCollection, DateTimeOffset utcNow)
        {
            var existingDataPoints = new HashSet<DateTimeOffset>(dataPointCollection.Select(x => x.TimeStamp));

            var midnight = utcNow.TimeToZero(); // get time at midnight

            for (var i = midnight.UtcDateTime.Minute; i < 1440; i++) // loop over every minute in the day
            {
                // if we dont have a datapoint for this current minute, add it
                var timeStamp = midnight.AddMinutes(i);
                if (!existingDataPoints.Contains(timeStamp))
                {
                    dataPointCollection.Add(new DataPoint() { TimeStamp = timeStamp, Value = 0.0 });
                }
            }

            return dataPointCollection.ToList();
        }
    }
}